package com.ask.service.businessService.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.ask.dto.Result;
import com.ask.dto.ScrollResult;
import com.ask.dto.UserDTO;
import com.ask.entity.Blog;
import com.ask.entity.Follow;
import com.ask.entity.LikeMessage;
import com.ask.entity.User;
import com.ask.mapper.BlogMapper;
import com.ask.service.businessService.IBlogService;
import com.ask.service.businessService.IFollowService;
import com.ask.service.businessService.IUserService;
import com.ask.threadlocals.UserHolder;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.ask.constants.RedisConstant.BLOG_LIKE_KEY;
import static com.ask.constants.RedisConstant.FEED_KEY;


@Service
@Slf4j
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {

    @Autowired
    private IUserService userService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private IFollowService followService;

    @Autowired
    private KafkaTemplate<String, LikeMessage> kafkaTemplate;

    @Override
    public Result queryBlogById(Long id) {
        Blog blog = getById(id);
        if (blog == null)
            return Result.fail("博客不存在！");
        queryBlogUser(blog);
        isBlogLiked(blog);
        return Result.success(blog);
    }

    /**
     * 判断当前blog是否被用户点赞过
     *
     * @param blog
     */
    private void isBlogLiked(Blog blog) {
        UserDTO user = UserHolder.getUser();
        if (user == null) // 用户未登录
            return;
        String key = BLOG_LIKE_KEY + blog.getId();
        Long userId = user.getId();
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        blog.setIsLike(score != null);
    }


    /**
     * 查询热门博客
     *
     * @param current
     * @return
     */
    @Override
    public Result queryHotBlog(Integer current) {
        Page<Blog> page = query()
                .orderByDesc("liked")
                .page(new Page<>(current, 10));
        List<Blog> blogs = page.getRecords();
        blogs.forEach(blog -> {
            this.queryBlogUser(blog);
            this.isBlogLiked(blog);
        });

        return Result.success(blogs);
    }


    /**
     * 博客点赞
     *
     * @param id
     * @return
     */
    @Override
    public Result likeBlog(Long id) {
        // 判断当前用户是否已经点赞
        Long userId = UserHolder.getUser().getId();
        String key = "blog:like:" + id;
        Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
        // 用户没有点赞过
        if (score == null) {
            boolean success = update().setSql("liked = liked + 1").eq("id", id).update();
            if (success) {
                stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
            }
        }
        // 用户已经点赞过
        else {
            boolean success = update().setSql("liked = liked - 1").eq("id", id).update();
            if (success)
                stringRedisTemplate.opsForZSet().remove(key, userId.toString());
        }
        return Result.success();
    }


    private static final DefaultRedisScript<Long> SCRIPT;

    static {
        SCRIPT = new DefaultRedisScript<>();
        SCRIPT.setLocation(new ClassPathResource("likeBlog.lua"));
        SCRIPT.setResultType(Long.class);
    }

    /**
     * Lua脚本优化博客点赞
     *
     * @param id
     * @return
     */
    @Override
    public Result likeBlog_V2(Long id) {
        // 获取当前用户
        Long userId = UserHolder.getUser().getId();

        // 执行lua脚本
        Long result = stringRedisTemplate.execute(
                SCRIPT,
                Collections.singletonList(id.toString()),
                userId.toString(),
                String.valueOf(System.currentTimeMillis())
        );

        // 跟新数据库
        // 初次点赞
        if (result == 1) {
            boolean success = update()
                    .setSql("liked = liked + 1").
                    eq("id", id).update();
            if (success) {
                log.info("用户{}成功对博客{}点赞", UserHolder.getUser().getId(), id);
                return Result.success("点赞成功！");
            }

        } else { // 重复点赞
            boolean success = update().setSql("liked = liked - 1").eq("id", id).update();
            if (success) {
                log.info("用户{}成功对博客{}取消点赞", UserHolder.getUser().getId(), id);
                return Result.success("取消点赞成功");
            }
        }
        return Result.fail("点赞失败！");
    }

    /**
     * 博客点赞（lua脚本 + Kafka-Mq优化）
     *
     * @param id
     * @return
     */
    public Result likeBlog_V3(Long id) {
        // 获取当前用户
        Long userId = UserHolder.getUser().getId();

        // 执行lua脚本
        Long result = stringRedisTemplate.execute(
                SCRIPT,
                Collections.singletonList(id.toString()),
                userId.toString(),
                String.valueOf(System.currentTimeMillis())
        );

        if (result == 1) {
            log.info("用户{}发送点赞消息，处理博客{}", userId, id);
            kafkaTemplate.send("like-topic", new LikeMessage(id, userId, LikeMessage.Action.LIKE));
            return Result.success("点赞成功！");

        } else {
            log.info("用户{}发送(取消)点赞消息，处理博客{}", userId, id);
            kafkaTemplate.send("like-topic", new LikeMessage(id, userId, LikeMessage.Action.UNLIKE));
            return Result.success("取消点赞成功！");
        }
    }


    /**
     * 查询博客的点赞排行
     *
     * @param id
     * @return
     */
    @Override
    public Result queryBlogLikes(Long id) {
        String key = BLOG_LIKE_KEY + id;
        Set<String> set = stringRedisTemplate.opsForZSet().range(key, 0, 4);
        if (set == null || set.isEmpty())
            return Result.success(Collections.emptyList());

        // 获取id集合
        List<Long> ids = set.stream().map(Long::valueOf).collect(Collectors.toList());
        String idStr = StrUtil.join(",", ids);
        List<UserDTO> userDTOs = userService.query()
                .in("id", ids).last("order by field(id, " + idStr + ")").list()
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.success(userDTOs);
    }

    /**
     * 新增博客
     *
     * @param blog
     * @return
     */
    @Override
    public Result saveBlog(Blog blog) {
        // 保存笔记至数据库
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();
        blog.setUserId(userId);
        blog.setName(user.getNickName());
        save(blog);
        // 查询撰写笔记用户的粉丝: select follow_user_id from tb_follow where user_id = userId
        List<Follow> follows = followService.query().eq("follow_user_id", userId).list();
        follows.forEach(follow -> {
            Long fanId = follow.getUserId();
            String key = FEED_KEY + fanId; // 将博客id推送给fans
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        });

        return Result.success();
    }

    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        // 从当前用户的信箱(zset)查询推送来的博客信息
        Long userId = UserHolder.getUser().getId();
        String key = FEED_KEY + userId;
        // 按分数从高到低查询博客 ID 和分数
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().
                reverseRangeByScoreWithScores(key, 0, max, offset, 2);

        if (typedTuples == null || typedTuples.isEmpty())
            return Result.success();

        // 解析zset的查询结果，计算offset和minTime
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0;
        int os = 1;
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
            ids.add(Long.valueOf(tuple.getValue())); // 收集博客id
            long time = tuple.getScore().longValue();
            if (time == minTime) {
                os++;
            } else {
                minTime = time;
                os = 1;
            }
        }

        // 根据ids查询博客信息
        String idStr = StrUtil.join(", ", ids);
        List<Blog> blogList = query()
                .in("id", ids)
                .last("order by field(id," + idStr + ")")
                .list();
        // 填充博客的用户信息和点赞状态
        for (Blog blog : blogList) {
            queryBlogUser(blog);
            isBlogLiked(blog);
        }

        // 封装结果返回·前端
        ScrollResult r = new ScrollResult();
        r.setList(blogList); // 博客列表
        r.setOffset(os); // 偏移量
        r.setMinTime(minTime); // 最小的分数（时间戳），用于下一次分页查询的起点。
        return Result.success(r);
    }

    /**
     * 查询博客的用户信息
     *
     * @param blog
     */
    private void queryBlogUser(Blog blog) {
        User user = userService.getById(blog.getUserId());
        blog.setName(user.getNickName());
        blog.setIcon(user.getIcon());
    }
}
